<?php
/**
 * Contians the main FPDF-5 class.
 *
 * A port of FPDF-1.6 by Olivier PLATHEY.
 *
 * @author Paul Horton <software AT phpsystems.co.uk>
 * @copyright Paul Horton (c) 2008-9
 * @package FPDF5
 * @see http://fpdf5.phpsystems.co.uk
 * @see http://www.fpdf.org
 * @version $Id: $
 */
namespace com\phpsystems\fpdf5;

if (!defined('FPDF5_LOADED') || FPDF5_LOADED !== true) {
   ini_set('include_path',
      ini_get('include_path') .
      PATH_SEPARATOR . dirname(__FILE__) .
      PATH_SEPARATOR . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fonts'
   );
   define('FPDF5_LOADED', true);
}

/**
 * Autload function.
 *
 * You may wish to override or replace this with your own.
 *
 * @param String $class_name
 */
function __autoload($str_class_name) {
    require_once($str_class_name . '.class.php');
}


/**
 * Main FPDF-5 class.
 *
 * @author Paul Horton <software AT phpsystems.co.uk>
 * @copyright Paul Horton (c) 2008-9
 * @package FPDF5
 */
class FPDF5 {

   /**
    * FPDF-5 Version String
    *
    */
   const VERSION = '1.0-pre';

   const ALIGNMENT_LEFT = 1;
   const ALIGNMENT_RIGHT = 2;
   const ALIGNMENT_CENTRE = 3;

   const INTERNAL_STATE_INIT = 0;
   const INTERNAL_STATE_OPENING_DOCUMENT = 1;
   const INTERNAL_STATE_IN_PAGE = 2;
   const INTERNAL_STATE_ENDING_DOCUMENT = 3;

   const INTERNAL_STATE_IN_HEADER = 10;
   const INTERNAL_STATE_IN_FOOTER = 11;

   /**
    * Current Page Number (page).
    *
    * page
    *
    * @var Integer
    */
   protected $int_current_page = 0;

   /**
    * Current page format.
    *
    * @var FPDF5_Page_Format
    */
   protected $obj_current_page_format = null;

   /**
    * Array of pages in the current PDF (pages).
    *
    * @var Array
    */
   protected $arr_pages = null;

   /**
    * Current Object Number (n).
    *
    * @var Integer
    */
   protected $int_current_object_number = 0;

   /**
    * Array of object offsets (offsets).
    *
    * @var Array
    */
   protected $arr_object_offsets = array();

   /**
    * Current internal state (state).
    *
    * @var Integer
    */
   protected $int_current_state = 0;

   /**
    * Current X position in pt.
    *
    * @var Float
    */
   protected $int_current_x = 0;

   /**
    * Current Y position in pt.
    *
    * @var Float
    */
   protected $int_current_y = 0;

   /**
    * Current line width in pt (LineWidth).
    *
    * @var FPDF5_Unit
    */
   protected $obj_current_line_width = 0;

   /**
    * Current Cell Margin.
    *
    * @var FPDF5_Unit
    */
   protected $obj_current_cell_margin = null;

   /**
    * Current Font (CurrentFont).
    *
    * @var FPDF5_Font
    */
   protected $obj_current_font = null;

   /**
    * Array of Fonts used in the PDF (fonts).
    *
    * @var Array
    */
   protected $arr_fonts_used = null;

   /**
    * Current Fill Colour.
    *
    * @var FPDF5_Colour
    */
   protected $obj_current_fill_colour = null;

   /**
    * Current Text Colour.
    *
    * @var FPDF5_Colour
    */
   protected $obj_current_text_colour = null;

   /**
    * Whether or not to allow auto page breaking.
    *
    * @var Boolean
    */
   protected $bol_auto_page_break = true;

   /**
    * Array of FPDF5_Image's used (images).
    *
    * @var Array
    */
   protected $arr_images_used = null;

   /**
    * In-memory buffer of PDF being created (buffer).
    *
    * @var mixed
    */
   private $mix_buffer = null;

   /**
    * Constructor
    *
    */
   public function __construct() {
      $this->init();
   }

   /**
    * Add a page to the PDF.
    *
    * @param FPDF5_Page_Format $obj_page_format
    */
   public function add_page($obj_page_format = null) {
      if (!is_null($obj_page_format) && !$obj_page_format instanceof FPDF5_Page_Format) {
         throw new FPDF5_Exception(__METHOD__ . ' Please supply an instance of FPDF5_Page_Format or NULL to use the default Page Format.');
      }

      if ($this->int_current_state == self::INTERNAL_STATE_INIT) {
         $this->set_current_state(self::INTERNAL_STATE_OPENING_DOCUMENT);
      }

      // Set FONT, COLOURS OTHERS

      // Add Footer Now is this is not the first page
      if ($this->int_current_page > 0) {
         $int_current_state = $this->int_current_state;
         $this->set_current_state(self::INTERNAL_STATE_IN_FOOTER);
         $this->draw_footer();
         $this->set_current_state($int_current_state);
         $this->end_page();
      }

      if (is_null($obj_page_format)) {
         $obj_page_format = $this->obj_current_page_format;
      }
      $this->begin_page($obj_page_format);

      // Set line cap style to square
      $this->out('2 J');
      // Set line width
      $this->out(sprintf('%.2F w', '0.57'));
      // Set font
      if (!is_null($this->obj_current_font)) {
         $this->set_font($this->obj_current_font);
      }

      // Set colours
//      $this->out('0 G');   // DRAW
      $this->obj_current_fill_colour = new FPDF5_Colour();
      $this->obj_current_text_colour = new FPDF5_Colour();

      // Add Header now
      $int_current_state = $this->int_current_state;
      $this->set_current_state(self::INTERNAL_STATE_IN_HEADER);
      $this->draw_header();
      $this->set_current_state($int_current_state);

      // Restore colours, fonts etc to before header
   }

   /**
    * Draw a Cell
    *
    * @param FPDF5_Unit $obj_width
    * @param FPDF5_Unit $int_height
    * @param String $str_text
    * @param Integer $int_next_position
    * @param Integer $int_align
    * @param String $str_border
    * @param Boolean $bol_fill
    * @param FPDF5_Link|null $mix_link
    */
   public function draw_cell(FPDF5_Unit $obj_width, $obj_height = null, $str_text = '', $int_next_position = 0, $int_align = '', $str_border = '', $bol_fill = false, $mix_link = null) {
      if (is_null($obj_height)) {
         $obj_height = FPDF5_Unit::create(0);
      }

      $this->try_page_break($obj_height);
      $str_data = '';

      // Fix if Width == 0
      if ($obj_width->get_unit_pt_value() == 0) {
         $obj_width = FPDF5_Unit::create(
            $this->obj_current_page_format->get_width()->get_unit_pt_value() - $this->obj_current_page_format->get_margin(FPDF5_Page_Format::MARGIN_RIGHT) - $this->int_current_x,
            FPDF5_Unit::UNIT_PT
         );
      }

      // Fill
      if ($bol_fill !== false || $str_border == 1) {
         $str_options = '';
         if ($bol_fill) {
            $str_options = ($str_border == 1) ? 'B' : 'f';
         } else {
            $str_options = 'S';
         }
         $str_data .= sprintf(
            '%.2F %.2F %.2F %.2F re %s ',
            $this->int_current_x,
            $this->obj_current_page_format->get_height()->get_unit_pt_value() - $this->int_current_y,
            $obj_width->get_unit_pt_value(),
            -($obj_height->get_unit_pt_value()),
            $str_options
         );
      }

      // Border
      if ($str_border !== '') {
         if (stristr($str_border, 'L')) {
            $str_data .= sprintf(
               '%.2F %.2F m %.2F %.2F l S ',
               $this->int_current_x,
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - $this->int_current_y,
               $this->int_current_x,
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($this->int_current_y + $obj_height->get_unit_pt_value())
            );
         }
         if (stristr($str_border, 'T')) {
            $str_data .= sprintf(
               '%.2F %.2F m %.2F %.2F l S ',
               $this->int_current_x,
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - $this->int_current_y,
               $this->int_current_x + $obj_width->get_unit_pt_value(),
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - $this->int_current_y
            );
         }
         if (stristr($str_border, 'R')) {
            $str_data .= sprintf(
               '%.2F %.2F m %.2F %.2F l S ',
               $this->int_current_x + $obj_width->get_unit_pt_value(),
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - $this->int_current_y,
               $this->int_current_x + $obj_width->get_unit_pt_value(),
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($this->int_current_y + $obj_height->get_unit_pt_value())
            );
         }
         if (stristr($str_border, 'B')) {
            $str_data .= sprintf(
               '%.2F %.2F m %.2F %.2F l S ',
               $this->int_current_x,
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($this->int_current_y + $obj_height->get_unit_pt_value()),
               $this->int_current_x + $obj_width->get_unit_pt_value(),
               $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($this->int_current_y + $obj_height->get_unit_pt_value())
            );
         }
      }

      // Text
      if ($str_text !== '') {
         switch ($int_align) {
            case self::ALIGNMENT_RIGHT:
               $int_dx = $obj_width->get_unit_pt_value() - $this->obj_current_cell_margin->get_unit_pt_value() - $this->obj_current_font->get_string_width($str_text);
               break;
            case self::ALIGNMENT_CENTRE:
               $int_dx = ($obj_width->get_unit_pt_value() - $this->obj_current_font->get_string_width($str_text)) / 2;
               break;
            case self::ALIGNMENT_LEFT:
            default:
               $int_dx = $this->obj_current_cell_margin->get_unit_pt_value();
               break;
         }

         // Set Text Colour
         $str_data .= 'q ' . $this->obj_current_text_colour->get_pdf_data() . ' ';

         $str_data .= sprintf(
            'BT %.2F %.2F Td (%s) Tj ET',
            $this->int_current_x + $int_dx,
            $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($this->int_current_y+0.5*$obj_height->get_unit_pt_value()+0.3*$this->obj_current_font->get_size()),
            str_replace(')','\\)', str_replace('(','\\(', str_replace('\\','\\\\', $str_text)))
         );

         if ($this->obj_current_font->is_underlined()) {
            $str_data .= ' ' . $this->obj_current_font->get_underline_pdf_data(
               $this->obj_current_page_format,
               $this->int_current_x + $int_dx,
               $this->int_current_y + 0.5 * $obj_height->get_unit_pt_value() + 0.3 * $this->obj_current_font->get_size(),
               $str_text
            );
         }

         $str_data .= ' Q';
      }

      if ($str_data !== '') {
         $this->out($str_data);
      }

      // Move internal position
      if ($int_next_position > 0) {
         // Next Line
         $this->int_current_y += $obj_height->get_unit_pt_value();
         if ($int_next_position == 1) {
            $this->int_current_x = $this->obj_current_page_format->get_margin(FPDF5_Page_Format::MARGIN_LEFT)->get_unit_pt_value();
         }
      } else {
         $this->int_current_x += $obj_width->get_unit_pt_value();
      }
   }

   /**
    * Draws an Image in the PDF.
    *
    * @param FPDF5_Image $obj_image
    * @param Integer $int_x
    * @param Integer $int_y
    * @param String $str_link
    */
   public function draw_image(FPDF5_Image $obj_image, $int_x = null, $int_y = null, $str_link = null) {
      if (!isset($this->arr_images_used[$obj_image->get_image_handle()])) {
         // First use of this image in this PDF.
         $this->arr_images_used[$obj_image->get_image_handle()] = array(
            'i' => count($this->arr_images_used),
            'image' => $obj_image
         );
      }

      $int_width = $obj_image->get_render_width()->get_unit_pt_value();
      $int_height = $obj_image->get_render_height()->get_unit_pt_value();

      // Flowing Mode
      if (is_null($int_y)) {
         $this->try_page_break(FPDF5_Unit::create($int_height, FPDF5_Unit::UNIT_IN));
      }

      if (is_null($int_x)) {
         $int_x = $this->int_current_x;
      }
      $this->out(
         sprintf(
            'q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q',
            $int_width,
            $int_height,
            $int_x,
            $this->obj_current_page_format->get_height()->get_unit_pt_value() - ($int_y + $int_height),
            $this->arr_images_used[$obj_image->get_image_handle()]['i']
         )
      );
   }

   /**
    * Output the PDF.
    *
    */
   public function output() {
      if ($this->int_current_state != self::INTERNAL_STATE_ENDING_DOCUMENT) {
         $this->end_document();
      }

      if (ob_get_length() > 0) {
         throw new FPDF5_Exception(__METHOD__ . ' Data already sent. Can\'t ouput PDF.');
      }

      if ($_GET['out'] == 1) {
         if (php_sapi_name() != 'cli') {
            header('Content-Type: application/pdf');
            header('Content-Length: ' . strlen($this->mix_buffer));
            header('Content-Disposition: inline; filename="sample.pdf"');
            header('Cache-Control: private, max-age=0, must-revalidate');
            header('Pragma: public');
            ini_set('zlib.output_compression', 0);
         }
      }
      echo $this->mix_buffer;
   }

   /**
    * Set whether or not auto-page breaking is enabled.
    *
    * @param Boolean $bol_auto_allow_page_breaking
    */
   public function set_allow_auto_page_break($bol_auto_allow_page_breaking) {
      $this->bol_auto_page_break = $bol_auto_allow_page_breaking;
   }

   /**
    * Set the current fill colour.
    *
    * @param FPDF5_Colour $obj_colour
    */
   public function set_fill_colour(FPDF5_Colour $obj_colour) {
      $this->obj_current_fill_colour = $obj_colour;
      if ($this->int_current_page > 0) {
         $this->out($this->obj_current_fill_colour->get_pdf_data());
      }
   }

   /**
    * Set the current Font.
    *
    * @param FPDF5_Font $obj_font
    */
   public function set_font(FPDF5_Font $obj_font) {
      // If this is the first time this font has been used, register it internally.
      if (!isset($this->arr_fonts_used[$obj_font->get_font_key()])) {
         // Could check if a core font here.
         $int_id = count($this->arr_object_offsets) + 1;
         $this->arr_fonts_used[$obj_font->get_font_key()] = array('font' => $obj_font);
      } else if ($obj_font === $this->obj_current_font) {
         // Font has already been used.
         return ;
      }


      $this->obj_current_font = $obj_font;
      if ($this->int_current_page > 0) {
         $this->out(sprintf('BT /F%d %.2F Tf ET', $this->get_font_index($this->obj_current_font->get_font_key()), $this->obj_current_font->get_size()));
      }
   }

   /**
    * Set the current text colour.
    *
    * @param FPDF5_Colour $obj_colour
    */
   public function set_text_colour(FPDF5_Colour $obj_colour) {
      $this->obj_current_text_colour = $obj_colour;
   }

   /**
    * Footer Content.
    *
    */
   protected function draw_footer() {}

   /**
    * Header Content.
    *
    */
   protected function draw_header() {}

   /**
    * Initialises the new PDF.
    *
    * @throws FPDF5_Exception
    */
   protected function init() {
      try {
         $this->environment_checks();
      } catch (FPDF5_Exception $obj_ex) {
         throw $obj_ex;
      }
        $this->int_current_page = 0;
        $this->obj_current_page_format = FPDF5_Page_Format::get_default();
        $this->int_current_object_number = 2;
        $this->arr_object_offsets = array();
        $this->mix_butter = '';
        $this->arr_pages = array();
        $this->int_current_state = self::INTERNAL_STATE_INIT;
        $this->obj_current_line_width = FPDF5_Unit::create(0.2, FPDF5_Unit::UNIT_MM);
        $this->obj_current_cell_margin = FPDF5_Unit::create(1, FPDF5_Unit::UNIT_MM);
   }

   /**
    * Checks environment and configured it if possible.
    *
    * @throws FPDF5_Exception
    */
   protected function environment_checks() {
      // Version 5.0.3 is required to support sprintf %F
      if (version_compare(PHP_VERSION, '5.0.3', '<')) {
         throw new FPDF5_Exception('PHP 5.0.3 is required for FPDF-5 to operate. Please come back whe you have upgraded!');
      }

      // mbstring overloading should not be enabled for now.
      if(ini_get('mbstring.func_overload') & 2) {
         throw new FPDF5_Exception('mbstring overloading must be disabled. See php.ini - mbstring.func_overload.');
      }

      // Disable runtime magic quotes
      if(get_magic_quotes_runtime()) {
         @set_magic_quotes_runtime(0);
      }
   }

   /**
    * Internally begins a document.
    *
    */
   private function begin_document() {}

   /**
    * Internally begin a page.
    *
    * @param FPDF5_Page_Format $obj_page_format
    */
   private function begin_page(FPDF5_Page_Format $obj_page_format) {
      $this->int_current_page++;
      $this->arr_pages[$this->int_current_page] = array($obj_page_format, '');
      $this->obj_current_page_format = $obj_page_format;
      $this->set_current_state(self::INTERNAL_STATE_IN_PAGE);

      $this->int_current_x = $this->obj_current_page_format->get_margin(FPDF5_Page_Format::MARGIN_LEFT)->get_unit_pt_value();
      $this->int_current_y = $this->obj_current_page_format->get_margin(FPDF5_Page_Format::MARGIN_TOP)->get_unit_pt_value();
   }

   /**
    * Internally ends a document.
    *
    */
   private function end_document() {
      if ($this->int_current_state == self::INTERNAL_STATE_ENDING_DOCUMENT) {
         return ;
      }

      // Do Last Footer.

      $this->end_page();

      // Put Header
      $this->out('%PDF-1.3');

      // Put Pages
      $int_kids_i = 0;
      $str_kids = '/Kids [';
      foreach ($this->arr_pages as $int_page_number => $arr_mix_page_data) {
         $this->new_pdf_object();
         $this->out('<</Type /Page');
         $this->out('/Parent 1 0 R');

         // Only if different page size to main doc.
         //$this->out(sprintf('/MediaBox [0 0 %.2F %.2F]', $arr_mix_page_data[0]->));

         $this->out('/Resources 2 0 R');

         // Output page links

         $this->out('/Contents ' . ($this->int_current_object_number+1) . ' 0 R>>');
         $this->out('endobj');

         $this->new_pdf_object();
         $this->out('<</Length ' . strlen($arr_mix_page_data[1]) . '>>');
         $this->put_stream($arr_mix_page_data[1]);
         $this->out('endobj');

         $str_kids .= (3+2*$int_kids_i) . ' 0 R ';
         $int_kids_i++;
      }
      $this->arr_object_offsets[1] = strlen($this->mix_buffer);
      $this->out('1 0 obj');
      $this->out('<</Type /Pages');
      $this->out($str_kids . ']');
      $this->out('/Count ' . count($this->arr_pages));
      $this->out(sprintf('/MediaBox [0 0 %.2F %.2F]', FPDF5_Page_Format::get_default()->get_width()->get_unit_pt_value(), FPDF5_Page_Format::get_default()->get_height()->get_unit_pt_value()));
      $this->out('>>');
      $this->out('endobj');

      // Put Resources
         // Put Fonts
         $int_n = $this->int_current_object_number;
            // Put Encoding Differences
            // Embed Fonts
         foreach ($this->arr_fonts_used as $str_font_key => $arr_font) {
            $obj_font = $arr_font['font'];
            $this->arr_fonts_used[$str_font_key]['object_number'] = $int_n+1;
            if ($obj_font->get_type() == FPDF5_Font::TYPE_CORE) {
               $this->new_pdf_object();
               $this->out('<</Type /Font');
               $this->out('/BaseFont /' . $obj_font->get_name());
               $this->out('/Subtype /Type1');
               if ($obj_font->get_name() != 'Symbol' && $obj_font->get_name() != 'ZapfDingbats') {
                  $this->out('/Encoding /WinAnsiEncoding');
               }
               $this->out('>>');
               $this->out('endobj');
            }
         }
         // Put Images
         $str_filter = '';
         reset($this->arr_images_used);
         foreach ($this->arr_images_used as $int_i => $arr_image) {
            $obj_image = $arr_image['image'];
            $this->new_pdf_object();
            $this->arr_images_used[$int_i]['n'] = $this->int_current_object_number;
            $this->out('<</Type /XObject');
            $this->out('/Subtype /Image');
            $this->out('/Width ' . $obj_image->get_image_width());
            $this->out('/Height ' . $obj_image->get_image_height());
            if ($obj_image->get_colour_space() == FPDF5_Image::COLOUR_SPACE_GRAY) {
              //$this->out('/ColorSpace [/Indexed /DeviceRGB -1 ' . ($this->arr_images_used[$int_i]['n']+1)); // TODO
            } else {
               switch ($obj_image->get_colour_space()) {
                  case FPDF5_Image::COLOUR_SPACE_CMYK:
                     $this->out('/ColorSpace /DeviceCMYK');
                     $this->out('/Decode [1 0 1 0 1 0 1 0]');
                     break;
                  case FPDF5_Image::COLOUR_SPACE_RGB:
                  default:
                     $this->out('/ColorSpace /DeviceRGB');
                     break;
               }
            }
            $this->out('/BitsPerComponent ' . $obj_image->get_bits());
            if ($obj_image->get_filter() != '') {
               $this->out('/Filter /' . $obj_image->get_filter());
            }
            if ($obj_image->get_pdf_parameters() != '') {
               $this->out($obj_image->get_pdf_parameters());
            }
            // - TRNS -
            // @todo
            // - TRNS -
            $this->out('/Length ' . strlen($obj_image->get_image_data()) . '>>');
            $this->put_stream($obj_image->get_image_data());
            $this->out('endobj');
            if ($obj_image->get_colour_space() == FPDF5_Image::COLOUR_SPACE_GRAY) {
               // Output Palette TODO
            }
         }

         // Resource Dictionary
         $this->arr_object_offsets[2] = strlen($this->mix_buffer);
         $this->out('2 0 obj');
         $this->out('<<');
         $this->out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
         $this->out('/Font <<');
         foreach ($this->arr_fonts_used as $str_font_key => $arr_font) {
            $obj_font = $arr_font['font'];
            $this->out('/F' . $this->get_font_index($obj_font->get_font_key()) . ' ' . $arr_font['object_number'] . ' 0 R');
         }
         $this->out('>>');
         $this->out('/XObject <<');
         foreach ($this->arr_images_used as $int_i => $arr_image) {
            $this->out('/I' . $arr_image['i'] . ' ' . $arr_image['n'] . ' 0 R');
         }
         $this->out('>>');
         $this->out('>>');
         $this->out('endobj');

      // INFO
      $this->new_pdf_object();
      $this->out('<<');
      $this->out('/Producer ' . $this->text_string('FDPF5 ' . self::VERSION));
         // Title, Subject, Author, Keywords, Creator
      $this->out('/CreationDate ' . $this->text_string('D:' . @date('YmdHis')));
      $this->out('>>');
      $this->out('endobj');

      // CATALOG
      $this->new_pdf_object();
      $this->out('<<');
      $this->out('/Type /Catalog');
      $this->out('/Pages 1 0 R');
         // Display on open stuff
         $this->out('/OpenAction [3 0 R /FitH null]');
         $this->out('/PageLayout /OneColumn');
      $this->out('>>');
      $this->out('endobj');


      // CROSS-REF
      $int_buffer_size = strlen($this->mix_buffer);
      $this->out('xref');
      $this->out('0 ' . ($this->int_current_object_number+1));
      $this->out('0000000000 65535 f ');
      for ($int_i = 1, $int_n = $this->int_current_object_number; $int_i <= $int_n; $int_i++) {
         $this->out(sprintf('%010d 00000 n ', $this->arr_object_offsets[$int_i]));
      }

      // TRAILER
      $this->out('trailer');
      $this->out('<<');
      $this->out('/Size '.($this->int_current_object_number+1));
      $this->out('/Root '.$this->int_current_object_number.' 0 R');
      $this->out('/Info '.($this->int_current_object_number-1).' 0 R');
      $this->out('>>');
      $this->out('startxref');
      $this->out($int_buffer_size);
      $this->out('%%EOF');

      // Change State
      $this->int_current_state = self::INTERNAL_STATE_ENDING_DOCUMENT;
   }

   /**
    * Internally end a page.
    *
    */
   private function end_page() {
      $this->set_current_state(self::INTERNAL_STATE_OPENING_DOCUMENT);
   }

   /**
    * Escape special characters in PDF data.
    *
    * @param String $str_string
    * @return String
    */
   private function escape($str_string) {
      $str_string = preg_replace(
        array('|\\\\|', '|\(|', '|\)|', "|\\r|"),
        array('\\\\\\\\', '\\\\(', '\\\\)', '\\\\r'),
        $str_string
      );
      return $str_string;
   }

   /**
    * Get internal Font index.
    *
    * @param String $str_font_key
    * @return Integer
    */
   private function get_font_index($str_font_key) {
      $arr_keys = array_keys($this->arr_fonts_used);
      foreach ($arr_keys as $int_i => $str_key) {
        if ($str_key == $str_font_key) {
           return $int_i+1;
        }
      }
      error_log('Couldn\'t determine font index for ' . $str_font_key);
      return -1;
   }

   /**
    * Create a new PDF object.
    *
    */
   private function new_pdf_object() {
      $this->int_current_object_number++;
      $this->arr_object_offsets[$this->int_current_object_number] = strlen($this->mix_buffer);
      $this->out($this->int_current_object_number . ' 0 obj');
   }

   /**
    * Outputs data to the PDF, or buffers it if necessary.
    *
    * @param String $str_data
    */
   private function out($str_data) {
      if (in_array($this->int_current_state, array(self::INTERNAL_STATE_IN_PAGE, self::INTERNAL_STATE_IN_HEADER, self::INTERNAL_STATE_IN_FOOTER))) {
         $this->arr_pages[$this->int_current_page][1] .= $str_data . "\n";
      } else {
         $this->mix_buffer .= $str_data . "\n";
      }
   }

   /**
    * Stream data to PDF.
    *
    * @param String $str_data
    */
   private function put_stream($str_data) {
      $this->out('stream');
      $this->out($str_data);
      $this->out('endstream');
   }

   /**
    * Set the current internal state.
    *
    * @param Integer $int_new_state
    */
   private function set_current_state($int_new_state) {
      $this->int_current_state = $int_new_state;
   }

   /**
    * Formats a text string for PDF data.
    *
    * @param String $str_data
    * @return String
    */
   private function text_string($str_data) {
      return '(' . $this->escape($str_data) . ')';
   }

   /**
    * Test whether we need to page break now, given that we want to add
    * something to the PDF of height <code>$obj_next_height</code>.
    *
    * @param FPDF5_Unit $obj_next_height
    */
   private function try_page_break(FPDF5_Unit $obj_next_height) {
      if ($this->int_current_y+$obj_next_height->get_unit_pt_value() > $this->obj_current_page_format->get_page_break_trigger()) {
         if ($this->int_current_state != self::INTERNAL_STATE_IN_HEADER && $this->int_current_state != self::INTERNAL_STATE_IN_FOOTER) {
            if ($this->bol_auto_page_break === true) {
               // Automatic page break
               $int_x = $this->int_current_x;
               // $ws
               //if (word spacing > 0) {
                  // $this->out('0 Tw');
               //}
               $this->add_page($this->obj_current_page_format);
               $this->int_current_x = $int_x;
               //if (word spacing > 0) {
                  // $this->out('%.3F Tw', $word_spacing_in_pt);
               //}
            }
         }
      }
   }
}